﻿namespace StockSharp.AlfaDirect
{
	using System;
	using System.Diagnostics;
	using System.Globalization;

	using ADLite;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.ComponentModel;

	using StockSharp.BusinessEntities;

	sealed class AlfaWrapper
	{
		const string _marketDepthTable = "queue"; // cтаканы
		const string _marketDepthFields = "sell_qty, price, buy_qty";

		const string _securityTable = "fin_info"; // инструменты
		const string _securityFields = "paper_no, ANSI_name, mat_date, status, p_code, place_code, curr_code, go_buy, go_sell, board_code, open_pos_qty, open_price, close_price, sell, sell_qty, buy, buy_qty, min_deal, max_deal, lot_size, volatility, theor_price";

		private const string _portfolioTable = "balance"; // позиции в разрезе рынков и портфелей
		private const string _portfolioFields = "acc_code, p_code, place_code, real_rest, daily_pl, paper_no, netto_rest";

		private const string _tradeTable = "all_trades"; // текущие сделки на рынке
		private const string _tradeFields = "paper_no, trd_no, qty, price, ts_time, b_s";

		private const string _orderTable = "orders";
		private const string _orderFields = "ord_no, acc_code, paper_no, status, b_s, price, qty, rest, ts_time, comments, place_code, stop_price, avg_trd_price";

		private const string _positionsTable = "balance"; // позиции в разрезе рынков и портфелей
		private const string _positionsFields = "acc_code, p_code, place_code, forword_rest, daily_pl, paper_no, netto_rest, income_rest, plan_rest, forword_rest, in_bids, in_offers";

		private const string _myTradesTable = "trades"; // сделки
		private const string _myTradesFields = "trd_no, ord_no, treaty, paper_no, price, qty, b_s, ts_time";

		private readonly SynchronizedDictionary<int, Order> _pendingOrders = new SynchronizedDictionary<int, Order>();

		private AlfaDirectClass _ad;
		private readonly EventDispatcher _dispatcher;

		private const string _adToken = "ad";
		private const string _dataToken = "data";

		public AlfaWrapper()
		{
			_dispatcher = new EventDispatcher(ex => ProcessDataError.SafeInvoke(ex));

			_dispatcher.Add(() =>
			{
				_ad = new AlfaDirectClass();
				_ad.OnConnectionChanged += ConnectionChanged;
				_ad.OnTableChanged += TableChanged;
				_ad.OrderConfirmed += OrderConfirmed;

			}, _adToken);
		}

		public void GetMarketTime()
		{
			_dispatcher.Add(() =>
			{
				var time = _ad.SessionTime;
				ProcessMarketTime.SafeInvoke(time);
			}, _adToken);
		}

		public event Action<string> ProcessSecurities;
		public event Action<string> ProcessTrades;
		public event Action<string, string> ProcessQuotes;
		public event Action<string> ProcessPortfolios;
		public event Action<string> ProcessOrders;
		public event Action<string> ProcessPositions;
		public event Action<string> ProcessMyTrades;
		public event Action<DateTime> ProcessMarketTime;

		public event Action Connected;
		public event Action Disconnected;
		public event Action<Exception> ConnectionError;
		public event Action NewDataExported;
		public event Action<Exception> ProcessDataError;

		private void TableChanged(string tableName, string tableParams, object data, object fieldtypes)
		{
			//Trace.WriteLine("tableName: " + tableName + ". tableParams: " + tableParams + ". data: " + (string)data);

			if(data == null)
			{
				return;
			}

			_dispatcher.Add(() =>
			{
				NewDataExported.SafeInvoke();

				switch (tableName)
				{
					case _marketDepthTable:
						ProcessQuotes.SafeInvoke(tableParams, (string)data);
						break;
					case _tradeTable:
						ProcessTrades.SafeInvoke((string)data);
						break;
					case _securityTable:
						ProcessSecurities.SafeInvoke((string)data);
						break;
					case _portfolioTable:
						ProcessPortfolios.SafeInvoke((string)data);
						ProcessPositions.SafeInvoke((string)data);
						break;
					case _orderTable:
						ProcessOrders.SafeInvoke((string)data);
						break;
					case _myTradesTable:
						ProcessMyTrades.SafeInvoke((string)data);
						break;
				}				
			}, _dataToken);
		}

		private void ConnectionChanged(eConnectionState state)
		{
			switch (state)
			{
				case eConnectionState.Connected:
					Connected.SafeInvoke();
					break;
				case eConnectionState.Disconnected:
					Disconnected.SafeInvoke();
					break;
				default:
					ConnectionError.SafeInvoke(new Exception("Error eConnectionState: " + state));
					break;
			}
		}

        public void Connect(string login, string password)
        {
			_dispatcher.Add(() =>
			{
				if (_ad.Connected)
				{
					Connected.SafeInvoke();
				}
				else
				{
					if (!login.IsEmpty() && !password.IsEmpty())
					{
						_ad.UserName = login;
						_ad.Password = password;
					}

					_ad.Connected = true;
				}
			}, _adToken);
        }

        public void Disconnect()
        {
            _ad.Connected = false;
        }

		public void StopExportOrders()
		{
			UnRegisterTable(_orderTable, string.Empty);
		}

		public void StopExportMyTrades()
		{
			UnRegisterTable(_myTradesTable, string.Empty);
		}

		public void StopExportPositions()
		{
			UnRegisterTable(_positionsTable, string.Empty);
		}

		public void RegisterPortfolio(Portfolio portfolio)
		{
			_dispatcher.Add(() => RegisterTable(_portfolioTable, _portfolioFields, GetFilter(portfolio)), _adToken);
		}

		public void RegisterSecurity()
		{
			_dispatcher.Add(() => RegisterTable(_securityTable, _securityFields, ""), _adToken);
		}

		public void RegisterQuotes(Security security)
		{
			_dispatcher.Add(() => RegisterTable(_marketDepthTable, _marketDepthFields, GetFilter(security)), _adToken);
		}

		public void RegisterTrades()
		{
			_dispatcher.Add(() => RegisterTable(_tradeTable, _tradeFields, ""), _adToken);
		}

		public void UnRegisterTrades()
		{
			_dispatcher.Add(() => UnRegisterTable(_tradeTable, ""), _adToken);
		}

		public void UnRegisterSecurity()
		{
			_dispatcher.Add(() => UnRegisterTable(_securityTable, ""), _adToken);
		}

		public void UnRegisterPortfolio(Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			// TODO: positions and portfolios use same table, so you will not receive updates for positions after that.
			UnRegisterTable(_portfolioTable, GetFilter(portfolio));
		}

		public void UnRegisterQuotes(Security security)
		{
			UnRegisterTable(_marketDepthTable, GetFilter(security));
		}

		private static string GetFilter(Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			return "treaty = " + portfolio.Name.Split("-")[0];
		}

		private static string GetFilter(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return "paper_no = {0}".Put(security.GetPaperNo());
		}

		private void RegisterTable(string tableName, string fields, string where)
		{
			if (tableName.IsEmpty())
				throw new ArgumentNullException("tableName");

			Trace.WriteLine("RegisterTable: {0}. Fields: {1}. Where: {2}".Put(tableName, fields, where));

			string message;
			var code = _ad.SubscribeTable(tableName, fields, where, eSubsctibeOptions.UpdatesOnly, out message);

			Trace.WriteLine("RegisterTable: msg {0}".Put(message));

			if (code != tagStateCodes.stcSuccess)
				throw new AlfaException(code, message);
		}

		private void UnRegisterTable(string tableName, string where)
		{
			if (tableName.IsEmpty())
				throw new ArgumentNullException("tableName");

			Trace.WriteLine("UnRegisterTable: {0}, where: {1}".Put(tableName, where));

			string message;
			var code = _ad.UnSubscribeTable(tableName, where, out message);

			if (code != tagStateCodes.stcSuccess)
				throw new AlfaException(code, message);
		}

		public void RegisterOrder(Order order)
		{
			_dispatcher.Add(() =>
			{
				var marketTime = _ad.SessionTime;

				var account = order.Portfolio.Name.Split("@")[0]; // Портфель
				var placeCode = order.Security.GetPlaceCode(); // Код торговой площадки (рынка).
				var pCode = order.Security.Code; // Код финансового инструмента.
				var endDate = AlfaUtils.ExpirityDateToAlfa(order.ExpiryDate, marketTime); // Срок действия поручения.
				var comments = order.Comment; // Комментарий.
				var currency = order.Security.GetCurrency(); // Валюта цены.
				var buySell = AlfaUtils.OrderDirectionsToAlfa(order.Direction); // Покупка/Продажа
				var quantity = (int)order.Volume; // Количество.
				var price = order.Price; // Цена.

				if (order.Type == OrderTypes.Conditional)
				{

					if(order.Condition == null)
					{
						using (order.BeginUpdate())
						{
							Trace.WriteLine("Error, stop condition is null");
							order.State = OrderStates.Failed;
						}
					}
					else
					{
						int id;

						var targetCondition = order.Condition as AlfaTargetLimitOrderCondition;
						
						if (targetCondition != null)
						{
							Trace.WriteLine("Stop-limit-target: price {0}, slippage {1}, target {2}"
								.Put(targetCondition.StopPrice, targetCondition.Slippage, targetCondition.TargetPrice));

							id = _ad.CreateStopOrder(account, placeCode, pCode, endDate, comments, currency, buySell, quantity,
								targetCondition.StopPrice, targetCondition.Slippage, targetCondition.TargetPrice, -1);
						}
						else
						{
							var limitCondition = order.Condition as AlfaLimitOrderCondition;
							if (limitCondition != null)
							{
								Trace.WriteLine("Stop-limit: price {0}, slippage {1}".Put(limitCondition.StopPrice, limitCondition.Slippage));

								id = _ad.CreateStopOrder(account, placeCode, pCode, endDate, comments, currency, buySell, quantity,
									limitCondition.StopPrice, limitCondition.Slippage, null, -1);						
							}
							else
							{
								Trace.WriteLine("Указанный тип стоп ордеров еще не поддерживается!");
								return;
							}
						}

						if (id == 0)
						{
							using (order.BeginUpdate())
							{
								Trace.WriteLine("Failed to register STOP order tr id {0}".Put(order.TransactionId));
								order.State = OrderStates.Failed;
							}
						}

						_pendingOrders.Add(id, order);

						Trace.WriteLine("CreateStopOrder: id = " + id + ", msg: " + _ad.LastResultMsg);
					}
				}
				else
				{
					if (order.Type == OrderTypes.Market)
					{
						var details = GetLocalDbData(_securityTable, _securityFields, GetFilter(order.Security));

						if (details.IsEmpty())
						{
							Trace.WriteLine("Ошибка расчета цены для маркет-заявки");
							order.State = OrderStates.Failed;
							order.Id = 0;
						}

						var data = details.Split('|');

						if (order.Direction == OrderDirections.Buy)
						{
							// best ask + 100 min price steps
							price = data[13].To<decimal>() + 100 * order.Security.MinStepSize;
						}
						else
						{
							// best bid - 100 min price steps
							price = data[15].To<decimal>() - 100 * order.Security.MinStepSize;
						}
					}

					Trace.WriteLine("CreateLimitOrder: " + price);

					var id = _ad.CreateLimitOrder(account, placeCode, pCode, endDate, comments, currency, buySell, quantity,
										 (double)price, null, null, null, null, null, "Y", null, null, null, null, null, null, null, null, null, null, -1);

					if(id == 0)
					{
						using (order.BeginUpdate())
						{
							Trace.WriteLine("Failed to register order tr id {0}".Put(order.TransactionId));
							order.State = OrderStates.Failed;
						}
					}

					_pendingOrders.Add(id, order);

					Trace.WriteLine("CreateLimitOrder: id = " + id + ", msg: " + _ad.LastResultMsg);
				}
			}, _adToken);
		}

//		public void ReRegisterOrder(Order oldOrder, Order newOrder)
//		{
//			_dispatcher.Add(() =>
//			{
//				var marketTime = _ad.SessionTime;
//
//				var endDate = AlfaUtils.ExpirityDateToAlfa(oldOrder.ExpiryDate, marketTime);
//
//				Trace.WriteLine("ReRegisterOrder. old Id: {0}, old tr id {1}, new price: {2}, vol: {3}, exp date: {4}".
//					Put(oldOrder.Id, oldOrder.TransactionId, newOrder.Price, newOrder.Volume, endDate));
//
//				_ad.UpdateLimitOrder((int)oldOrder.Id, newOrder.Price, newOrder.Volume, endDate, -1);				
//			}, _adToken);
//		}

		public void CancelOrder(Order order)
		{
			_dispatcher.Add(() =>
			{
				Trace.WriteLine("Drop order. Id: {0}, Tr id: {1}".Put(order.Id, order.TransactionId));

				long id;

				using(order.BeginRead())
				{
					id = order.Id;
				}

				if (id == 0)
				{
					Trace.WriteLine("Drop order error Id = 0! Tr. id: {0}".Put(order.TransactionId));
				}
				else
				{
					Trace.WriteLine("AD Drop order. Id: {0}".Put(id));
					_ad.DropOrder(id, null, null, null, null, null, -1);
				}
			}, _adToken);
		}

		public void CancelOrders(bool? isStopOrder, Portfolio portfolio, OrderDirections? direction, string classCode, Security security)
		{
			_dispatcher.Add(() =>
			{
				var isBuySell = (direction == null) ? null : AlfaUtils.OrderDirectionsToAlfa(direction.Value);

				var account = portfolio.Name.Split("@")[0];
				var pCode = security.Code.Split("@")[0];

				_ad.DropOrder(null, isBuySell, null, account, null, pCode, -1);				
			}, _adToken);
		}

		string GetLocalDbData(string table, string fields, string where = "")
		{
			//NewDataExported.SafeInvoke();

			return _ad.GetLocalDBData(table, fields, where);
		}

		/// <summary>
		/// Получить информацию о минимальном шаге и его стоимости.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="step">Минимальный шаг цены.</param>
		/// <param name="cost">Стоимость шага цены.</param>
		public void GetPriceStepInfo(Security security, out decimal step, out decimal cost)
		{
			step = 0;
			cost = 0;

			const string table = "papers";
			const string fields = "price_step, price_step_cost";
			var where = GetFilter(security);

			var data = GetLocalDbData(table, fields, where);

			if (data == null)
			{
				return;
			}

			var details = data.Split('|');

			step = Decimal.Parse(details[0], NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
			cost = Decimal.Parse(details[1], NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint);
		}

		public string GetExchangeCode(string placeCode)
		{
			const string table = "trade_places";
			const string fields = "place_code, place_name, ex_code";
			var where = "place_code = " + placeCode;

			var data = GetLocalDbData(table, fields, where);

			return data.IsNull() ? String.Empty : data.Split('|')[2];
		}

		public string GetHistoryData(Security security, AlfaTimeFrames timeFrame, Range<DateTime> range)
		{
			const int modeStoreLocal = 2;
			const int maxWaitTimeSec = 10 * 60;

			//Trace.WriteLine("Start GetHistoryData: {0} - {1}".Put(range.Min, range.Max));

			var data = (string)_ad.GetArchiveFinInfo(security.GetPlaceCode(), security.Code,
				timeFrame.Interval, range.Min, range.Max, modeStoreLocal, maxWaitTimeSec);

			//Trace.WriteLine("End GetHistoryData: {0} - {1}".Put(range.Min, range.Max));

			return data;
		}

		public string GetLocalHistoryData(Security security, AlfaTimeFrames timeFrame, Range<DateTime> range)
		{
			//Trace.WriteLine("Start GetLocalHistoryData: {0} - {1}".Put(range.Min, range.Max));

			var data = _ad.GetArchiveFinInfoFromDB(security.GetPlaceCode(), security.Code,
			                                       timeFrame.Interval, range.Min, range.Max);

			//Trace.WriteLine("End GetLocalHistoryData: {0} - {1}".Put(range.Min, range.Max));

			if(_ad.LastResult != StateCodes.stcSuccess)
			{
				Trace.WriteLine("ERROR fetching local data: {0}".Put(_ad.LastResultMsg));
			}

			return data;
		}

		private void OrderConfirmed(int id, int ordernum, string message, eCommandResult status)
		{
			Trace.WriteLine("OrderConfirmed: id " + id + ", ordernum " + ordernum + ", msg " + message + ", res " + status);

			if(_pendingOrders.ContainsKey(id))
			{
				var order = _pendingOrders[id];
				_pendingOrders.Remove(id);

				if(status == eCommandResult.crSuccess)
				{
					using(order.BeginUpdate())
					{
						order.Id = ordernum;
						order.State = OrderStates.Active;						
					}

					var where = "ord_no = {0}".Put(id);

					var data = GetLocalDbData(_orderTable, _orderFields, where);

					if(data == null)
					{
						return;
					}

//					// FORTS: 92424531|41469-000|45442|O|S|9491|1|1|0:00:00||FORTS|0|0|
//					// MICEX: 92429469|41469-000|12910|O|B|93.9|10|10|18.04.2012 13:56:25||MICEX_SHR|0|0|
//
//					var d = data.Split("|");
//					d[9] = order.TransactionId.ToString(CultureInfo.InvariantCulture);
//					data = string.Join("|", d);

					_dispatcher.Add(() => ProcessOrders.SafeInvoke(data), _dataToken);
				}
				else
				{
					// Заявка не была принята из-за превышения интенсивности.
					// Отправляем на повторную регистрацию.
					Trace.WriteLine("Повторная регистрация заявки {0}".Put(order.TransactionId));
					RegisterOrder(order);	
				}
			}
		}

		public void StartExport()
		{
			_dispatcher.Add(() =>
			{
				// загрузить портфели.
				var data = GetLocalDbData(_portfolioTable, _portfolioFields);

				var portfolioData = data;
				_dispatcher.Add(() => ProcessPortfolios.SafeInvoke(portfolioData), _dataToken);

				// загрузить инструменты
				data = GetLocalDbData(_securityTable, _securityFields);

				var securityData = data;
				_dispatcher.Add(() => ProcessSecurities.SafeInvoke(securityData), _dataToken);

				// Загрузить список всех заявок.
				data = GetLocalDbData(_orderTable, _orderFields);

				var orderData = data;
				_dispatcher.Add(() => ProcessOrders.SafeInvoke(orderData), _dataToken);

				// Загрузить список всех сделок.
				data = GetLocalDbData(_myTradesTable, _myTradesFields);

				var myTradesData = data;
				_dispatcher.Add(() => ProcessMyTrades.SafeInvoke(myTradesData), _dataToken);

				// Прочитать позии
				data = GetLocalDbData(_positionsTable, _positionsFields);
				var positionData = data;
				_dispatcher.Add(() => ProcessPositions.SafeInvoke(positionData), _dataToken);
				
				RegisterTable(_orderTable, _orderFields, "");
				RegisterTable(_positionsTable, _positionsFields, "");
				RegisterTable(_myTradesTable, _myTradesFields, "");

			}, _adToken);
		}

		public void StopExport()
		{
			//UnRegisterTrades();
		}
	}
}
